package ru.spb.nicetu.altx.mnemo.node;

import ru.spb.nicetu.altx.mnemo.cell.INodeCell;
import ru.spb.nicetu.altx.mnemo.extpoint.model.typeConverter.IConverter;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.IProxyValueType;
import ru.spb.nicetu.altx.mnemo.extpoint.valuetypes.ITemplateValueType;
import ru.spb.nicetu.altx.mnemo.math.managers.ValueTypeClassManager;
import ru.spb.nicetu.altx.mnemo.math.managers.ValueTypeConverterManager;

// @SuppressWarnings("serial")
public class DataNode extends BaseNode implements IDataNode {

  /**
   *
   */
  private static final long serialVersionUID = 3406774095395567939L;
  private boolean isInput = false;
  private boolean isOutput = false;

  @SuppressWarnings("unchecked")
  private transient ITemplateValueType data;

  public DataNode(String name) {
    super(name);
  }

  public boolean isInput() {
    return isInput;
  }

  public void setIsInput(boolean isInput) {
    this.isInput = isInput;
    // FIXME DataNode.setIsInput() - грубая очистка cellLinks.
    cellLinks.clear();
  }

  public boolean isOutput() {
    return isOutput;
  }

  public void setIsOutput(boolean isOutput) {
    this.isOutput = isOutput;
  }

  @Override
  public void calcGoodValueSourceCondition() {
    if (isInput()) {
      // Если это - "входной Data-NODE", то НЕ ОН должен
      // беспокоиться об источниках значения
      setGoodValueSourceCondition(true);
    } else {
      super.calcGoodValueSourceCondition();
    }
  }

  @Override
  @SuppressWarnings("unchecked")
  public Object getAdapter(Class adapter) {
    return super.getAdapter(adapter);
  }

  @Override
  @SuppressWarnings("unchecked")
  public void run() {
    setWasRunned(false);
    // fWasRunned = false;

    // Достаем источник значения для единственной ВХ Ячейки
    INodeCell myCell = getAllInputCells().get(0);
    ICellLink link = getLink(myCell);

    if (link == null) {
      fWasRunned = false;
      return;
    }

    INode targetNode = link.getNode();
    INodeCell targetCell = link.getCell();

    if ((targetNode == null) || (targetCell == null)) {
      fWasRunned = false;
      return;
    }

    // Достаем ЗНАЧЕНИЕ из источника значений
    ITemplateValueType inputVal = targetNode.getCellValue(targetCell);
    if (inputVal == null) {
      fWasRunned = false;
      return;
    }

    // Получаем классы значений, которое поступило и значения, хранящегося в
    // ячейке
    Class<? extends ITemplateValueType> inputValClass
      = ValueTypeClassManager.getInstance()
        .getValueTypeClass(targetCell.getValueClassName());
    Class<? extends ITemplateValueType> myDataClass
      = ValueTypeClassManager.getInstance()
        .getValueTypeClass(myCell.getValueClassName());

    // Если классы совпадают, то копируем ЗНАЧЕНИЕ к себе и заканчиваем работу
    if (inputValClass.equals(myDataClass)) {
      data = inputVal.clone();
      fWasRunned = true;
      return;
    }

    // Классы не совпадают, необходимо попробовать выполнить приведение типов
    if (false == ValueTypeConverterManager.getInstance().canCastTo(
        inputValClass, myDataClass)) {
      fWasRunned = false;
      return;
    }

    // Приведение можно выполнить. Пробуем найти однозначную дополнительную
    // информацию для приведения к более широкому типу
    Object additionalInfo = null;
    String myDataClassName = myDataClass.getName();

    // Если наш источник данных - CompoundTaskNode, то доп. инфу не ищем, т.к.
    // неизвестно, каким образом внутри него происходили преобразования.
    // Иначе...
    if (false == targetNode instanceof CompoundTaskNode) {
      // Проходим по всем входным ячейка нашего источника данных. Для каждой
      // ячейке определяем её источника и сравниваем тип значения поставляемого
      // этим источником и наш. Если наш типы совпадает только с одним
      // источником,
      // то считается, что однозначная доп. инфа получена, иначе нет.
      for (INodeCell targetInputCell : targetNode.getAllInputCells()) {
        ICellLink targetInputLink = targetNode.getLink(targetInputCell);

        INodeCell candidateCell = targetInputLink.getCell();
        if (candidateCell == null)
          continue;

        String candidateClassName = candidateCell.getValueClassName();

        if (myDataClassName.equals(candidateClassName)) {
          if (additionalInfo != null) { // Т.е. доп инфа найдена у второго
            // источника
            additionalInfo = null;
            break;
          } else {
            INode candidateNode = targetInputLink.getNode();
            if (candidateNode == null)
              continue;

            additionalInfo = candidateNode.getCellValue(candidateCell);
          }
        }
      }
    }

    IConverter converter = ValueTypeConverterManager.getInstance()
        .getConverter(inputValClass, myDataClass);
    if (converter == null) {
      data = inputVal.clone();

      fWasRunned = true;
      return;
    }
    Object myNewValue = converter
        .convert(inputVal, myDataClass, additionalInfo);

    if (myNewValue == null) {
      // Копируем ЗНАЧЕНИЕ к себе
      data = inputVal.clone();
    } else
      data = (ITemplateValueType) myNewValue;

    fWasRunned = true;
  }

  @Override
  @SuppressWarnings("unchecked")
  public ITemplateValueType getCellValue(INodeCell cell) {
    if (!(getAllOutputCells().contains(cell) || getAllInputCells().contains(
        cell)))
      return null;
    if (wasRunned() == false)
      return null;

    // FIXME VALUE-MEMORY-Problem
    // если в Ячейке лежит Proxy, то отдадим Original
    if (data instanceof IProxyValueType)
      return ((IProxyValueType) data).getOriginalValue();
    // end VALUE-MEMORY-Problem

    return data;
  }

  @Override
  @SuppressWarnings("unchecked")
  public void setCellValue(INodeCell cell, ITemplateValueType value) {
    if (!(getAllOutputCells().contains(cell) || getAllInputCells().contains(
        cell)))
      return;
    data = value;
  }

  @Deprecated
  @SuppressWarnings("unchecked")
  protected ITemplateValueType getUnsafeData() {
    return data;
  }
}
